
package com.yuanmei.datas;

import android.content.Context;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.yuanmei.model.HistoryDayQuote;
import com.yuanmei.model.RealTimeQuote;
import com.yuanmei.model.StockItem;
import com.yuanmei.model.TickQuote;
import com.yuanmei.model.TodayQuote;
import com.yuanmei.common.Utils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;
import jxl.Sheet;
import jxl.Workbook;
import jxl.read.biff.BiffException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/*
*
网易财经 API

方法3：
    http://img1.money.126.net/data/[沪深拼音]/[是否复权]/day/history/[年份]/[股票代码].json
    返回结果：获取日线数据。
    其中，是否复权，不复权为 kline，复权为 klinederc。

    例如，http://img1.money.126.net/data/hs/kline/day/history/2015/1399001.json，获取深证成指2015年所有日线数据。

方法4：
    http://img1.money.126.net/data/[沪深拼音]/[是否复权]/[周期]/times/[股票代码].json
    返回结果：获取日线所有时间节点和收盘价。
        其中，[是否复权]，不复权为 kline，复权为 klinederc。
        其中，[周期]，day为日数据，week周数据，month月数据。
        例如，http://img1.money.126.net/data/hs/kline/day/times/1399001.json，获取深证成指所有时间节点数据。


使用 JQuery+json 获取163 股票信息 https://blog.csdn.net/moneyice/article/details/7877030


* */

@SuppressWarnings("deprecation")
public class NetEaseWebSource {
    private static final String TAG = NetEaseWebSource.class.getName();

    private final static SimpleDateFormat sNetEaseRequestDateFormat
            = new SimpleDateFormat("yyyyMMdd", Locale.CHINA);
    private final static SimpleDateFormat sYearDateFormat
            = new SimpleDateFormat("yyyy", Locale.CHINA);

    // set default start date to 1990-01-01: year - 1900, month - 1, day = day - 1
    private final static Date sDefaultStartDate = new Date(90, 0, 0);

    private static final Map<String, String> sNetEaseHeaderMap = new HashMap<>();

    static {
        sNetEaseHeaderMap.put("日期", "date");
        sNetEaseHeaderMap.put("股票代码", "code");
        sNetEaseHeaderMap.put("名称", "name");
        sNetEaseHeaderMap.put("开盘价", "open");
        sNetEaseHeaderMap.put("最高价", "high");
        sNetEaseHeaderMap.put("最低价", "low");
        sNetEaseHeaderMap.put("收盘价", "close");
        sNetEaseHeaderMap.put("前收盘", "yclose");
        sNetEaseHeaderMap.put("涨跌额", "change");
        sNetEaseHeaderMap.put("涨跌幅", "pchg");
        sNetEaseHeaderMap.put("成交量", "volume");
        sNetEaseHeaderMap.put("成交金额", "amount");
        sNetEaseHeaderMap.put("换手率", "turnover");
        sNetEaseHeaderMap.put("总市值", "tcap");
        sNetEaseHeaderMap.put("流通市值", "mcap");
        sNetEaseHeaderMap.put("成交笔数", "deals");
    }

    private static Map<String, Integer> getNetEaseIndexMap(String inNetEaseHeader) {
        String[] netEaseHeaderArray = inNetEaseHeader.split(",");
        Map<String, Integer> headerIndexMap = new HashMap<>();
        for (int i = 0; i < netEaseHeaderArray.length; i++) {
            String translateHeaderName = sNetEaseHeaderMap.get(netEaseHeaderArray[i]);
            headerIndexMap.put(translateHeaderName, i);
        }
        return headerIndexMap;
    }

    private static void parseCsvToRealm(
            BufferedReader inBufferReader,
            Realm realm) {
        try {
            String header = inBufferReader.readLine(); // Get header
            Map<String, Integer> indexMap = getNetEaseIndexMap(header);
            realm.beginTransaction();
            String line;
            while ((line = inBufferReader.readLine()) != null) {
                String[] fieldValues = line.split(",");
                HistoryDayQuote quote = new HistoryDayQuote(fieldValues, indexMap);
                if (!quote.isQualified()) {
                    continue;
                }
                realm.insertOrUpdate(quote);
            }
            realm.commitTransaction();
        } catch (IOException e) {
            Log.d(TAG, "Add NetEase history quote occur error ", e);
        }
    }

    /*
      * 方法6：http://quotes.money.163.com/service/chddata.html?code=[股票代码]&start=[开始日期]&end=[结束日期]&fields=[自定义列]
        返回结果：历史股价及相关情况；CSV文件,该方法为网易公开方法，推荐使用。其中，自定义列可定义:
        TCLOSE收盘价;HIGH最高价;LOW最低价;TOPEN开盘价;LCLOSE前收盘价;CHG涨跌额;PCHG涨跌幅;
        TURNOVER换手率;VOTURNOVER成交量;VATURNOVER成交金额;TCAP总市值;MCAP流通市值这些值。
        例如，
        http://quotes.money.163.com/service/chddata.html?code=0601857&start=19900101&end=20150618&fields=TCLOSE;HIGH;LOW;TOPEN;LCLOSE;CHG;PCHG;TURNOVER;VOTURNOVER;VATURNOVER;TCAP;MCAP
        获取0601857从2007-11-05到2015-06-18区间的数据。
        */

    static void updateHistoryDayLine(String netEaseCode, Realm realm, Date endDate) {
        Date lastRecordDate;
        RealmResults<HistoryDayQuote> results = realm.where(HistoryDayQuote.class).findAll();
        lastRecordDate = sDefaultStartDate;
        if (results.size() > 0) {
            Number maxDate = results.max("date");
            if (maxDate != null) {
                lastRecordDate = Utils.getDateFromIDate(maxDate.intValue());
            }
        }
        long deltaMills = endDate.getTime() - lastRecordDate.getTime();
        if (deltaMills < Utils.MILLIS_PER_DAY) {
            return;
        }

        Date startDate = new Date(lastRecordDate.getTime() + Utils.MILLIS_PER_DAY);
        Map<String, String> params = new LinkedHashMap<>();
        params.put("code", netEaseCode);
        params.put("start", sNetEaseRequestDateFormat.format(startDate));
        params.put("end", sNetEaseRequestDateFormat.format(endDate));
//        params.put("fields",   "TOPEN;HIGH;LOW;TCLOSE;VOTURNOVER;VATURNOVER;TURNOVER;PCHG");
        String reqUrl = "http://quotes.money.163.com/service/chddata.html" + "?"
                + Utils.buildUrlParameters(params) + " ";
        try {
            URL request = new URL(reqUrl);
            URLConnection connection = request.openConnection();
            BufferedReader inBufferReader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), Charset.forName("GBK")));
            parseCsvToRealm(inBufferReader, realm);
            inBufferReader.close();
        } catch (Exception e) {
            Log.e(TAG, "Netease update HistoryDatabase fail, url:" + reqUrl, e);
        }
    }
/*
   方法7 查询股票信息 使用如下格式的请求
    http://api.money.126.net/data/feed/1000002,1000001,1000881,money.api
        股票代码之间使用逗号分隔，股票代码的命名规则参见 网易财经的规则。
   返回的json 结果如下：
   _ntes_quote_callback({"1000002":{"code": "1000002", "percent": -0.002375, "high": 16.95, "askvol3": 58600, "askvol2": 21700, "askvol5": 44200, "askvol4": 26300,
  "price": 16.8, "open": 16.93, "bid5": 16.76, "bid4": 16.77, "bid3": 16.78, "bid2": 16.79, "bid1": 16.8, "low": 16.57, "updown": -0.04, "type": "SZ",
  "bidvol1": 79016, "status": 0, "bidvol3": 65000, "bidvol2": 12200, "symbol": "000002", "update": "2022/09/02 17:59:58", "bidvol5": 32500,
  "bidvol4": 36500, "volume": 69680411, "askvol1": 59752, "ask5": 16.85, "ask4": 16.84, "ask1": 16.81,
  "name": "\u4e07  \u79d1\uff21", "ask3": 16.83, "ask2": 16.82, "arrow": "\u2193",
  "time": "2022/09/02 16:00:58", "yestclose": 16.84, "turnover": 1165902392.37} });
*/

    static ArrayList<String> fetchTodayQuotes(
            List<String> codeList,
            Realm realm) {
        // http://api.money.126.net/data/feed/1000002,1000001,1000881,money.api
        if (codeList.size() < 1)
            return null;

        StringBuilder sbUrl = new StringBuilder("http://api.money.126.net/data/feed/");
        for (String netEaseCode : codeList) {
//            Log.d(TAG, "JKP debug append stock:" + item.netEaseCode() + "_" + item.name());
            sbUrl = sbUrl.append(netEaseCode).append(",");
        }
        sbUrl.append("money.api");
        String jsonQuote = "";
        int timeoutMilliSeconds = 300 * 1000;
//        String exceptionInfo = "No Exception";
        try {
            URL u = new URL(sbUrl.toString());
            HttpURLConnection c = (HttpURLConnection) u.openConnection();
            c.setRequestMethod("GET");
            c.setRequestProperty("Content-length", "0");
            c.setRequestProperty("Accept", "application/json");
            c.setUseCaches(false);
            c.setAllowUserInteraction(false);
            c.setConnectTimeout(timeoutMilliSeconds);
            c.setReadTimeout(timeoutMilliSeconds);
            c.connect();
            if (HttpURLConnection.HTTP_OK == c.getResponseCode()) {
                BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                br.close();
                String response = sb.toString();
                response = response.substring(response.indexOf("(") + 1, response.indexOf(")"));
                JSONObject root_json = new JSONObject(response);
                Gson gs = new Gson();
                ArrayList<String> resultCodeList = new ArrayList<>();

                for (String netEaseCode : codeList) {
                    jsonQuote = root_json.getString(netEaseCode);
                    RealTimeQuote rtQuote = gs.fromJson(jsonQuote, RealTimeQuote.class);
//                    quotesMap.put(item.netEaseCode(), rtQuotes);
                    TodayQuote quote;
                    try {
                        quote = new TodayQuote(netEaseCode, rtQuote);
                    } catch (NullPointerException e) {
                        Log.e(TAG, "construct TodayQuote error\n json=" + jsonQuote, e);
                        continue;
                    }
                    resultCodeList.add(netEaseCode);
                    realm.beginTransaction();
                    realm.insertOrUpdate(quote);
                    realm.commitTransaction();
                }
                return resultCodeList;
            }
            c.disconnect();
        } catch (JsonSyntaxException e) {
            Log.e(TAG, "Format json error\n json=" + jsonQuote, e);
//            exceptionInfo = "Format json error\n json=" + jsonQuote;
        } catch (InterruptedIOException e) {
            Log.e(TAG, "Fetch io error at url:" + sbUrl.toString());
//            exceptionInfo = "Fetch io error at url:" + sbUrl.toString();
        } catch (Exception e) {
            Log.e(TAG, "Fetch market quotes fail for unknown exception:", e);
//            exceptionInfo = "Fetch market quotes fail for unknown exception:\n" + e.toString();
        }
//        Toast.makeText(ctx, exceptionInfo, Toast.LENGTH_SHORT).show();

        return null;
    }

    /*
    * 方法1：http://img1.money.126.net/data/[沪深拼音]/time/today/[股票代码].json
    返回结果：当日分时图数据；JSON数据；
       分时图获取数据依次是count节点数量、symbol股票代码、name股票名称、data数据，
       其中数据依次是小时分钟时间、价格、均价、成交量。
    注意，沪深拼音为简写hs，以此可以推断出其他市场也可以获取，具体请自行判断研究。

    例如，http://img1.money.126.net/data/hs/time/today/1399001.json，返回深证成指当日分时图数据。

    {"count":242,"symbol":"399001","name":"\u6df1\u8bc1\u6210\u6307","data":[["0930",7615.802,7615.802,163758973],
    ["0931",7590.203,7603.003,646654909],["0932",7603.211,7603.072,227576513],


    http://img1.money.126.net/data/hs/time/today/0600063.json
    http://img1.money.126.net/data/hs/time/today/1000063.json
    * */

    // 网易实际是1分钟交易数据
    private static void updateMinuteQuoteToRealm(String symbol, Realm realm) {
        String reqUrl = "http://img1.money.126.net/data/hs/time/today/"
                + symbol + ".json";
        try {
            Request request = new Request.Builder()
                    .url(reqUrl)
                    .build();
            OkHttpClient client = new OkHttpClient();

            Response response = client.newCall(request).execute();
            if (response.isSuccessful() && response.body() != null) {
                String bodyString = response.body().string();
                JSONObject resultObj = new JSONObject(bodyString);
                JSONArray dataArray = resultObj.getJSONArray("data");
                realm.beginTransaction();
                for (int i = 0; i < dataArray.length(); i++) {
                    JSONArray data = dataArray.getJSONArray(i);
                    String minuteTick = data.getString(0);
                    float closePrice = (float) data.getDouble(2);
                    float volume = data.getInt(3);
                    realm.insertOrUpdate(new TickQuote(minuteTick, volume, closePrice));
                }
                realm.commitTransaction();
            }

        } catch (JSONException | IOException e) {
            e.printStackTrace();
        }
    }

    private static void UpdateMinuteTickToRealm(String netEaseCode, String realmFile) {
        RealmConfiguration tickRealmConfig = new RealmConfiguration.Builder()
                .name(realmFile)
                .deleteRealmIfMigrationNeeded()
                .build();
        Realm realm = Realm.getInstance(tickRealmConfig);
        updateMinuteQuoteToRealm(netEaseCode, realm);
        realm.close();
        if (!Realm.compactRealm(tickRealmConfig)) {
            Log.w(TAG, "After NetEase update " + netEaseCode + " today tick quotes, compact realm fail");
        }
    }

    public static List<TickQuote> getRealTimeTickData(String netEaseCode, Date date) {
        String realmFile = Utils.getRealTimeTickRealmFile(netEaseCode, date);
        UpdateMinuteTickToRealm(netEaseCode, realmFile);
        return Utils.getTickQuotesFromRealm(realmFile);
    }

    private static void replaceTodayMinuteTickOfRealm(String netEaseCode, String realmFile) {
        RealmConfiguration tickRealmConfig = new RealmConfiguration.Builder()
                .name(realmFile)
                .deleteRealmIfMigrationNeeded()
                .build();
        Realm realm = Realm.getInstance(tickRealmConfig);
        realm.beginTransaction();
        realm.deleteAll();
        realm.commitTransaction();
        updateMinuteQuoteToRealm(netEaseCode, realm);
        realm.close();
        if (!Realm.compactRealm(tickRealmConfig)) {
            Log.w(TAG, "After NetEase update " + netEaseCode + " today tick quotes, compact realm fail");
        }
    }

    static List<TickQuote> replaceTodayMinuteTickData(String netEaseCode, Date date) {
        String realmFile = Utils.getTodayMinuteTickRealmFile(netEaseCode, date);
        replaceTodayMinuteTickOfRealm(netEaseCode, realmFile);
        return Utils.getTickQuotesFromRealm(realmFile);
    }

    // 财务指标
    // http://quotes.money.163.com/f10/zycwzb_601336,season.html


    /*     // 交易明细

    wget http://quotes.money.163.com/cjmx/2021/20210621/1000063.xls
    1000063.xls:

    成交时间	成交价	价格变动	成交量（手）	成交额（元）	性质
    09:30:01	33.85	-0.07	1345	4557947	卖盘
    09:30:03	33.9	0.05	2895	9807016	买盘
    09:30:06	33.87	-0.03	1913	6480877	卖盘
    09:30:10	33.87	0	1179	3991997	卖盘
    09:30:14	33.81	-0.06	902	3052600	卖盘

     * */
    private static final Map<String, String> sTickFieldsMap = new HashMap<>();

    static {
        sTickFieldsMap.put("成交时间", "tick");
        sTickFieldsMap.put("成交价", "price");
        sTickFieldsMap.put("价格变动", "price_change");
        sTickFieldsMap.put("成交量（手）", "volume");
        sTickFieldsMap.put("成交额（元）", "amount");
        sTickFieldsMap.put("性质", "type");
    }

    private static Map<String, Integer> matchFieldsMap(Sheet sheet) {
        if (sheet.getColumns() < 6) {
            return null;
        }
        Map<String, Integer> fieldsMap = new HashMap<>();
        for (int i = 0; i < sheet.getColumns(); i++) {
            String normalName =
                    sTickFieldsMap.get(sheet.getCell(i, 0).getContents());
            if (normalName == null)
                return null;

            fieldsMap.put(normalName, i);
        }
        return fieldsMap;
    }

    /*
     * 数据不准，有重复，有遗漏
     * */
    private static String downloadTickDataToRealm(
            String netEaseCode,
            Date tickDate,
            Realm realm) {
        String errorString = "ok";

        String yearString = sYearDateFormat.format(tickDate);
        String dateString = sNetEaseRequestDateFormat.format(tickDate);
        String reqUrl = "http://quotes.money.163.com/cjmx/"
                + yearString + "/"
                + dateString + "/"
                + netEaseCode + ".xls";
        try {
            URL request = new URL(reqUrl);
            HttpURLConnection conn = (HttpURLConnection) request.openConnection();
            conn.setReadTimeout(3000);
            conn.addRequestProperty("Content-Type", Utils.CONTENT_TYPE);
            conn.addRequestProperty("User-Agent", Utils.HTTP_AGENT);
            conn.addRequestProperty("Referer", "stock.gtimg.cn");
            conn.setRequestMethod("GET");
            int resp = conn.getResponseCode();
            Log.i(TAG, "jkp netease tick cjmx ResponseCod(" + resp
                    + "),URL:" + reqUrl);
            if (resp != HttpURLConnection.HTTP_OK) {
                if (resp == HttpURLConnection.HTTP_MOVED_TEMP
                        || resp == HttpURLConnection.HTTP_MOVED_PERM
                        || resp == HttpURLConnection.HTTP_SEE_OTHER) {
                    String newUrl = conn.getHeaderField("Location");
                    String cookies = conn.getHeaderField("Set-Cookie");

                    conn = (HttpURLConnection) new URL(newUrl).openConnection();
                    conn.setRequestProperty("Cookie", cookies);
                    conn.addRequestProperty("Content-Type", Utils.CONTENT_TYPE);
                    conn.addRequestProperty("User-Agent", Utils.HTTP_AGENT);
                    conn.addRequestProperty("Referer", "quotes.money.163.com");

                    if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                        errorString = "fail: jkp netease tick: resp=" + conn.getResponseCode();
                        Log.w(TAG, errorString);
                        return errorString;
                    }
                } else {
                    errorString = "fail: jkp netease tick: resp:" + conn.getResponseCode();
                    Log.w(TAG, errorString);
                    return errorString;
                }
            }

            Workbook tickBook = Workbook.getWorkbook(conn.getInputStream());
            Sheet sheet0 = tickBook.getSheet(0);
            Map<String, Integer> fieldsMap = matchFieldsMap(sheet0);
            if (fieldsMap == null) {
                errorString = "fail: jkp netease tick sheet name:"
                        + sheet0.getName();
                Log.w(TAG, errorString);
                return errorString;
            }

            realm.beginTransaction();
            realm.deleteAll();
            realm.commitTransaction();

            realm.beginTransaction();
            int total_rows = sheet0.getRows();
            int amount_col = fieldsMap.get("amount");
            int volume_col = fieldsMap.get("volume");
            int price_col = fieldsMap.get("price");
            int type_col = fieldsMap.get("type");
            int tick_col = fieldsMap.get("tick");
            for (int i = 1; i < total_rows; i++) {
                float amount = Utils.safeToFloat(sheet0.getCell(
                        amount_col, i).getContents());
                if (amount <= 0) {
                    continue;
                }

                float volume = Utils.safeToFloat(
                        sheet0.getCell(volume_col, i).getContents());
                if (volume <= 0) {
                    continue;
                }

                String price_string = sheet0.getCell(price_col, i).getContents();
                float price = Utils.safeToFloat(price_string);
                if (price <= 0) {
                    continue;
                }

                String type = sheet0.getCell(type_col, i).getContents();
                String tick = sheet0.getCell(tick_col, i).getContents();

//                if (tick.startsWith("09:50:")) {
//                    Log.w(TAG,
//                            "JKPD i=" + i + ", tick=" + tick + ",price="+ price_string + ", vol=" + volume);
//                }

                TickQuote quote = new TickQuote(
                        tick, amount, volume, price, type);
                realm.insertOrUpdate(quote);
            }

            realm.commitTransaction();

            return errorString;
        } catch (NullPointerException e) {
            errorString = "fail jkp netease tick for get NullPointerException";
            Log.e(TAG, errorString, e);
        } catch (IOException e) {
            errorString = "fail jkp netease tick for get IOException";
            Log.e(TAG, errorString, e);
        } catch (BiffException e) {
            errorString = "fail jkp netease tick for get excel BiffException";
            Log.e(TAG, errorString, e);
        }
        return errorString;
    }

    public static String downloadHistoryTickData(String netEaseCode, Date tickDate) {
        RealmConfiguration tickRealmConfig = new RealmConfiguration.Builder()
                .name(Utils.getTickRealmFile(netEaseCode, tickDate))
                .deleteRealmIfMigrationNeeded()
                .build();
        Realm realm = Realm.getInstance(tickRealmConfig);
        String errorString = downloadTickDataToRealm(netEaseCode, tickDate, realm);
        realm.close();
        if (!Realm.compactRealm(tickRealmConfig)) {
            Log.w(TAG, "After net_ease update tick(cjmx) data, compact realm fail");
        }
        return errorString;
    }
}
